﻿using System;
using System.Collections;
using System.ComponentModel;
using System.Drawing;
using System.Linq;
using System.Windows.Forms;

namespace BS.FormControls
{
    /// <summary>
    /// This is a special ComboBox that each item may conatins an image.
    /// </summary>
    public class ImagedComboBox : ComboBox
    {
        private ImagedComboCollection<ImagedComboBoxItem> _items;

        /// <summary>
        /// The imaged ComboBox items.
        /// this property is invisibile for design serializer.
        /// </summary>
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public new ImagedComboCollection<ImagedComboBoxItem> Items 
        { 
            get {return _items; }
            set { _items = value; }
        }

        public ImagedComboBox()
        {
            //Specifies that the list is displayed by clicking the down arrow and that the text portion is not editable. 
            //This means that the user cannot enter a new value. 
            //Only values already in the list can be selected. The list displays only if 
            DropDownStyle = ComboBoxStyle.DropDownList; 
            //All the elements in the control are drawn manually and can differ in size.
            DrawMode = DrawMode.OwnerDrawVariable;
            //using DrawItem event we need to draw item
            DrawItem += ComboBoxDrawItemEvent;
            MeasureItem += ComboBoxMeasureItemEvent;
        }
      
        protected override ControlCollection CreateControlsInstance()
        {
            _items = new ImagedComboCollection<ImagedComboBoxItem>
            {
                ItemsBase = base.Items
            };

            _items.UpdateItems += UpdateItems;
            
            return base.CreateControlsInstance();
        }
      
        /// <summary>
        /// Handles UpdateItems event which fired when an item, added, removed or inserted.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void UpdateItems(object sender, EventArgs e)
        {
        }

        /// <summary>
        /// I have set the Draw property to DrawMode.OwnerDrawVariable, so I must caluclate the item measurement.  
        /// I will set the height and width of each item before it is drawn. 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ComboBoxMeasureItemEvent(object sender, MeasureItemEventArgs e)
        {
            var g = CreateGraphics();
            var maxWidth = 0;
            foreach (var width in Items.ItemsBase.Cast<object>().Select(element => (int)g.MeasureString(element.ToString(), Font).Width).Where(width => width > maxWidth))
            {
                maxWidth = width;
            }
            DropDownWidth = maxWidth + 20;
        }

        /// <summary>
        /// Draws overrided items.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ComboBoxDrawItemEvent(object sender, DrawItemEventArgs e)
        {
            //Draw backgroud of the item
            e.DrawBackground();
            if (e.Index != -1)
            {
                var comboboxItem = Items[e.Index];
                //Draw the image in combo box using its bound and ItemHeight
                e.Graphics.DrawImage(comboboxItem.Image, e.Bounds.X, e.Bounds.Y, ItemHeight, ItemHeight);

                //we need to draw the item as string because we made drawmode to ownervariable
                e.Graphics.DrawString(Items[e.Index].Value.ToString(), Font, Brushes.Black,
                                      new RectangleF(e.Bounds.X + ItemHeight, e.Bounds.Y, DropDownWidth,
                                                     ItemHeight));
            }
            //draw rectangle over the item selected
            e.DrawFocusRectangle();
        }
    }

    /// <summary>
    /// Collections of ComboBoxItem.
    /// </summary>
    /// <typeparam name="TComboBoxItem">ComboBoxItem.</typeparam>
    public class ImagedComboCollection<TComboBoxItem> : CollectionBase
    {

        public EventHandler UpdateItems;
        public ComboBox.ObjectCollection ItemsBase { get; set; }

        public ImagedComboBoxItem this[int index]
        {
            get
            {
                return ((ImagedComboBoxItem)ItemsBase[index]);
            }
            set
            {
                ItemsBase[index] = value;
            }
        }

        public int Add(ImagedComboBoxItem value)
        {
            var result = ItemsBase.Add(value);
            UpdateItems.Invoke(this, null);
            return result;
        }

        public int IndexOf(ImagedComboBoxItem value)
        {
            return (ItemsBase.IndexOf(value));
        }

        public void Insert(int index, ImagedComboBoxItem value)
        {
            ItemsBase.Insert(index, value);
            UpdateItems.Invoke(this, null);
        }

        public void Remove(ImagedComboBoxItem value)
        {
            ItemsBase.Remove(value);
            UpdateItems.Invoke(this, null);
        }

        public bool Contains(ImagedComboBoxItem value)
        {
            return (ItemsBase.Contains(value));
        }

    }

    /// <summary>
    /// This class represents an ComboBox item of the ImagedComboBox which may contains an image and value.
    /// 
    /// </summary>
    [Serializable]
    public class ImagedComboBoxItem
    {
        private object _value;
        private Image _image;

        /// <summary>
        /// ComobBox Item.
        /// </summary>
        public object Value
        {
            get
            {
                return _value;
            }
            set
            {
                _value = value;
            }
        }

        /// <summary>
        /// Item image.
        /// </summary>
        public Image Image
        {
            get
            {
                return _image;
            }
            set
            {
                _image = value;
            }
        }

        public ImagedComboBoxItem()
        {
            _value = String.Empty;
            _image = new Bitmap(1, 1);
        }

        /// <summary>
        /// Constructor item without image.
        /// </summary>
        /// <param name="value">Item value.</param>
        public ImagedComboBoxItem(object value)
        {
            _value = value;
            _image = new Bitmap(1, 1);

        }

        /// <summary>
        ///  Constructor item with image.
        /// </summary>
        /// <param name="value">Item value.</param>
        /// <param name="image">Item image.</param>
        public ImagedComboBoxItem(object value, Image image)
        {
            _value = value;
            _image = image;
        }

        public override string ToString()
        {
            return _value.ToString();
        }
    }
}
